/*
 * Stok3d - getting you stoked on Flash Player native 3D
 * Copyright (C) 2009  David Lenaerts
 * 
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 * 
 * Author information:
 * David Lenaerts
 * url: http://www.derschmale.com
 * mail: david.lenaerts@gmail.com
 * 
 */
package com.derschmale.stok3d.utils
{
	import flash.display.BitmapData;
	import flash.display.Shader;
	import flash.filters.ShaderFilter;
	import flash.geom.ColorTransform;
	import flash.geom.Point;
	
	/**
	 * The NormalMapUtil is a set of static functions to generate and manipulate normal maps.
	 */
	public class NormalMapUtil
	{
		[Embed(source="/../pbj/NormalFromHeight.pbj", mimeType="application/octet-stream")]
		private static var NormalFromHeight : Class;
		
		[Embed(source="/../pbj/NormalizedFromHeight.pbj", mimeType="application/octet-stream")]
		private static var NormalizedFromHeight : Class;
		
		private static var _normalFromHeightFilter : ShaderFilter = new ShaderFilter(new Shader(new NormalFromHeight()));
		private static var _normalizedFromHeightFilter : ShaderFilter = new ShaderFilter(new Shader(new NormalizedFromHeight()));
		private static var _origin : Point = new Point();
		
		/**
		 * Generates a normal map from a height map
		 * 
		 * @param heightMap The greyscale height map from which the normal map will be generated
		 * @param zBias The initial value of the z-coordinate for the normals. If normalized is true, a higher value will cause the normal map to be more flat. zBias cannot be 0.
		 * @param normalized Indicates whether the generated normals should be unit-length.
		 * 
		 * @return The generated normal map.
		 */
		public static function generateFromHeightMap(heightMap : BitmapData, zBias : Number = 1, normalized : Boolean = true) : BitmapData
		{
			var normalMap : BitmapData = new BitmapData(heightMap.width, heightMap.height, false, zBias*0xff);
			var shader : Shader = _normalFromHeightFilter.shader;
			shader.data.zBias.value = [ zBias ];
			
			if (normalized)
				normalMap.applyFilter(heightMap, heightMap.rect, _origin, _normalizedFromHeightFilter);
			else
				normalMap.applyFilter(heightMap, heightMap.rect, _origin, _normalFromHeightFilter);
			
			return normalMap;
		}
		
		/**
		 * Generates a normal map from a height map and draws it to an existing BitmapData instance. Use this if the normal map has to be updated rather than generated once. For example: when the height map is animated.
		 * 
		 * @param target The BitmapData that will contain the generated
		 * @param heightMap The greyscale height map from which the normal map will be generated
		 * @param zBias The initial value of the z-coordinate for the normals. If normalized is true, a higher value will cause the normal map to be more flat. zBias cannot be 0.
		 * @param normalized Indicates whether the generated normals should be unit-length.
		 */
		public static function drawFromHeightMap(target : BitmapData, heightMap : BitmapData, zBias : Number = 1, normalized : Boolean = true) : void
		{
			var shader : Shader = normalized? _normalizedFromHeightFilter.shader : _normalFromHeightFilter.shader;
			shader.data.zBias.value = [ zBias ];
			
			if (normalized)
				target.applyFilter(heightMap, heightMap.rect, _origin, _normalizedFromHeightFilter);
			else
				target.applyFilter(heightMap, heightMap.rect, _origin, _normalFromHeightFilter);
		}
		
		/**
		 * Inverts the x-coordinates of the normals
		 */
		public static function invertX(normalMap : BitmapData) : void
		{
			var colorTransform : ColorTransform = new ColorTransform(-1, 1, 1, 1, 0xff, 0, 0, 0);
			normalMap.colorTransform(normalMap.rect, colorTransform);
		}
		
		/**
		 * Inverts the y-coordinates of the normals
		 */
		public static function invertY(normalMap : BitmapData) : void
		{
			var colorTransform : ColorTransform = new ColorTransform(1, -1, 1, 1, 0, 0xff, 0, 0);
			normalMap.colorTransform(normalMap.rect, colorTransform);
		}
		
		/**
		 * Inverts both the x and y-coordinates of the normals
		 */
		public static function invertXY(normalMap : BitmapData) : void
		{
			var colorTransform : ColorTransform = new ColorTransform(-1, -1, 1, 1, 0xff, 0xff, 0, 0);
			normalMap.colorTransform(normalMap.rect, colorTransform);
		}
		
		/**
		 * Sets all the normals in the map as unit-length
		 */
		public static function normalize(normalMap : BitmapData) : void
		{
			var x : int = normalMap.width, y : int;
			var pix : uint;
			var r : Number, g : Number, b : Number;
			var len : Number;
			
			while (x--) {
				y = normalMap.height;
				while (y--) {
					pix = normalMap.getPixel(x, y);
					r = ((pix >> 16 & 0xff) / 0xff - 0.5) * 2;
					g = ((pix >> 8 & 0xff) / 0xff - 0.5) * 2;
					b = (pix  & 0xff) / 0xff;
					len = Math.sqrt(r*r+g*g+b*b);
					r /= len*2;
					g /= len*2;
					b /= len;
					r += 0.5;
					g += 0.5;
					pix = (int(r*0xff) << 16)+ (int(g*0xff) << 8) + b*0xff;
					normalMap.setPixel(x, y, pix);
				}
			}
		}
	}
}